package metamodel.visitor;

import metamodel.*;

import java.util.Iterator;
import java.util.Collection;
import java.util.HashSet;

/**
 * todo: add switchable collection visitor mode
 * (transparent collection and explicit collection)
 * perhaps the mode could be indicated by the type of Visitor???
 * User: felix
 * Date: 09.06.2005
 */
public class ObjectGraphIterator {
    

    public static class TraversalMode {
        String code;

        /** Assumes that the Graph is tree in respect to the visitor description given.
         *  <strong>Note</strong> that cycles may lead to an infinite loop (when recursive data structures are involved).
         *  Cycle refers to a cycle in the undirected graph which is induced by the navigation graph*/
        public static final TraversalMode ASSUME_TREE=new TraversalMode("assume tree");
        /** The visitor checks whether every object is reached only once. If this is not the case an
         * CycleException is thrown.*/
        public static final TraversalMode CHECK_TREE=new TraversalMode("check tree");
        /** The visitor checks for cycles and on multiple navigations to an object notifies the visitior
         *  and does not cycle.
         *  @see ObjectGraphVisitor#visitCyclic(metamodel.ClassNode,metamodel.DeepProperty,Object,Object,boolean) */
        public static final TraversalMode NOTIFY_CYCLES =new TraversalMode("notify cycles");

        private TraversalMode(String code) {
            this.code = code;
        }

        public String toString() {
            return "TraversalMode: "+code;
        }

    }

    private ClassNode classNode;
    private ObjectGraphVisitor visitor;
    private TraversalMode mode;
    private HashSet visited;

    /**  Instantiates Iterator with mode @see ObjectGraphIterator.TraversalMode#NOTIFY_CYCLES*/
    public ObjectGraphIterator(ClassNode classNode, ObjectGraphVisitor visitor){
        this(classNode,visitor,TraversalMode.NOTIFY_CYCLES);
    }

    public ObjectGraphIterator(ClassNode classNode, ObjectGraphVisitor visitor, TraversalMode mode){
        this.classNode = classNode;
        this.visitor = visitor;
        this.mode = mode;

        if (mode!=TraversalMode.ASSUME_TREE){
            visited=new HashSet(10);
        }
    }

    public void iterateCollection (Collection list){
        for (Iterator iterator = list.iterator(); iterator.hasNext();) {
            Object data = iterator.next();
                if (!visited.contains(data)){
                    typeCase(classNode, null, data,null, false);
                }
        }
    }


    public void iterate (Object data){
        typeCase(classNode, null, data,null, false);
    }

    private void typeCase(ClassNode classNode, DeepProperty dp, Object data, Object parent, boolean hasNext) {
              if (mode!=TraversalMode.ASSUME_TREE){
             if (visited.contains(data)){
                 if (mode==TraversalMode.CHECK_TREE){
                         throw new CycleException();
                 }
                 visitor.visitCyclic(dp.getElementType(),dp,data,parent,hasNext);
                 return;   // and yes it's a leaf
             }
             //visited.add(data);
            }
        if (classNode.isPoly()){
              ClassNode concreteClassNode = classNode.getPoly().getClazzForConcreteTypeName(data.getClass().getName());
              typeCase(concreteClassNode,dp, data,parent,hasNext);
              return;
        }
        iterateRecord(classNode, dp,data, parent,hasNext);
    }

    private void iterateRecord(ClassNode classNode, DeepProperty dp,Object data, Object parent,boolean hasNext) {
        if (parent==null){
            visitor.visitRoot(classNode,data);
        } else{
            visitor.visitObject(classNode,dp, data, parent);
        }
        for (Iterator iterator = classNode.getProperties().iterator(); iterator.hasNext();) {
            Property p =  (Property) iterator.next();
            iterateProperty(p, data,iterator.hasNext());
        }
        if (parent==null){
            visitor.endRoot(classNode,data);
        }   else {
            visitor.endObject(classNode,dp, data, parent,hasNext);
        }

    }

    private void iterateProperty(Property p, Object data, boolean hasNext) {

        final Object propValue = p.getValue(data);
        if (propValue==null){
            visitor.visitNull(p,data,hasNext);
            return ; //leaf node by definition
        }


        if (p instanceof FlatProperty){
            FlatProperty flatProp=(FlatProperty) p;
            visitor.visitFlatProperty(flatProp,propValue,data,hasNext);
            return ; //leaf node
        }


        if (p instanceof ObjectReference){
            ObjectReference objRef = (ObjectReference) p;
            typeCase(objRef.getElementType(), objRef, propValue,data,hasNext);
            return;
        }

        if (p instanceof CollectionRef){
            CollectionRef collRef=(CollectionRef) p;
            Collection c=(Collection) propValue;
            iterateObjectCollection(collRef, c, data,hasNext);
            return;
        }
        throw new RuntimeException("Unknown meta type");
    }



    private void iterateObjectCollection(CollectionRef collRef, Collection data, Object parent, boolean hasNext) {
        int index=0;
        visitor.visitCollection(collRef, data,parent);

        for (Iterator iterator = data.iterator(); iterator.hasNext();) {
            Object collectionElement =  iterator.next();
            if (collectionElement==null){
                visitor.visitNull(collRef,data,hasNext);
                continue;
            }
            typeCase(collRef.getElementType(), collRef, collectionElement,data, iterator.hasNext());
            index++;
        }
        visitor.endCollection(collRef, data,parent,hasNext);

    }
}
